package com.imooc.pan.server.modules.file.service.impl;

import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.google.common.collect.Lists;
import com.imooc.pan.core.constants.RPanConstants;
import com.imooc.pan.core.exception.RPanBusinessException;
import com.imooc.pan.core.utils.FileUtils;
import com.imooc.pan.core.utils.IdUtil;
//import com.imooc.pan.server.common.mq.config.RabbitMQConfig;
import com.imooc.pan.server.common.mq.event.file.DeleteFileEvent;
import com.imooc.pan.server.common.mq.event.search.UserSearchEvent;
import com.imooc.pan.server.common.utils.HttpUtil;
import com.imooc.pan.server.modules.file.constants.FileConstants;
import com.imooc.pan.server.modules.file.context.*;
import com.imooc.pan.server.modules.file.converter.FileConverter;
import com.imooc.pan.server.modules.file.entity.RPanFile;
import com.imooc.pan.server.modules.file.entity.RPanFileChunk;
import com.imooc.pan.server.modules.file.entity.RPanUserFile;
import com.imooc.pan.server.modules.file.enums.DelFlagEnum;
import com.imooc.pan.server.modules.file.enums.FileTypeEnum;
import com.imooc.pan.server.modules.file.enums.FolderFlagEnum;
import com.imooc.pan.server.modules.file.service.IFileChunkService;
import com.imooc.pan.server.modules.file.service.IFileService;
import com.imooc.pan.server.modules.file.service.IUserFileService;
import com.imooc.pan.server.modules.file.mapper.RPanUserFileMapper;
import com.imooc.pan.server.modules.file.vo.*;
import com.imooc.pan.storage.engine.core.StorageEngine;
import com.imooc.pan.storage.engine.core.context.ReadFileContext;
//import com.imooc.pan.stream.core.IStreamProducer;
//import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.servlet.http.HttpServletResponse;
import javax.swing.tree.ExpandVetoException;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.util.*;
import java.util.stream.Collectors;

/**
 * @author lenovo
 * @description 针对表【r_pan_user_file(用户文件信息表)】的数据库操作Service实现
 * @createDate 2023-07-27 18:36:52
 */
@Service
public class UserFileServiceImpl extends ServiceImpl<RPanUserFileMapper, RPanUserFile>
        implements IUserFileService {

    @Autowired
    private IFileService iFileService;

    @Autowired
    private IFileChunkService iFileChunkService;

    @Autowired
    private FileConverter fileConverter;

    @Autowired
    private StorageEngine storageEngine;

//    @Autowired
//    private RabbitTemplate rabbitTemplate;

    //@Autowired
    //@Qualifier(value = "defaultStreamProducer")
    //private IStreamProducer producer;
    //private ApplicationContext applicationContext;
    //
    //public void setApplicationContext(ApplicationContext applicationContext) {
    //    this.applicationContext = applicationContext;
    //}

    @Override
    public RPanUserFile getUserRootFileInfo(Long userId) {
        QueryWrapper<RPanUserFile> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("user_id", userId);
        queryWrapper.eq("parent_id", FileConstants.TOP_PARENT_ID);
        queryWrapper.eq("del_flag", DelFlagEnum.NO.getCode());
        queryWrapper.eq("folder_flag", FolderFlagEnum.YES.getCode());
        return getOne(queryWrapper);
    }

    @Override
    public Long createFolder(CreateFolderContext createFolderContext) {
        return saveUserFile(createFolderContext.getParentId(), createFolderContext.getFolderName(),
                FolderFlagEnum.YES, null, null,
                createFolderContext.getUserId(), null);
    }

    @Override
    public List<RPanUserFileVO> getFileList(QueryFileListContext context) {
        return baseMapper.selectFileList(context);
    }

    /**
     * 更新文件的条件满不满足
     * 更新文件名
     *
     * @param context UpdateFilenameContext
     */
    @Override
    public void updateFilename(UpdateFilenameContext context) {
        checkUpdateFilenameCondition(context);
        doUpdateFilename(context);
    }

    /**
     * 批量删除用户文件
     * 1. 校验
     * 2. 执行删除操作
     * 3. 发布批量删除文件的事件，给其它模块订阅使用
     *
     * @param context DeleteFileContext
     */
    @Override
    public void deleteFile(DeleteFileContext context) {
        checkFileDeleteCondition(context);
        doDeleteFile(context);
        // 对外发布文件删除的事件
        afterFileDelete(context);
    }

    /**
     * 文件秒传
     * <p></p>
     * 上传了已经存在的文件，处理重复命名并关联文件实体即可
     * <p>
     * 1、通过文件的唯一标识查找实体文件
     * <p>
     * 2、没有查到，秒传失败
     * <p>
     * 3、如果查到了，直接挂载记录，返回秒传成功
     *
     * @param context SecUploadFileContext
     * @return
     */
    @Override
    public boolean secUpload(SecUploadFileContext context) {
        RPanFile record = getFileByUserIdAndIdentifier(context.getUserId(), context.getIdentifier());
        if (Objects.isNull(record)) {
            return false;
        }
        saveUserFile(context.getParentId(), context.getFilename(), FolderFlagEnum.NO,
                FileTypeEnum.getFileTypeCode(FileUtils.getFileSuffix(context.getFilename())),
                record.getFileId(), context.getUserId(), record.getFileSizeDesc());
        return true;
    }

    /**
     * 1. 上传文件并保存实体文件记录 r_pan_file
     * 2. 保存用户与文件的关系记录 r_pan_user_file
     *
     * @param context
     */
    @Transactional(rollbackFor = Exception.class)
    @Override
    public void upload(FileUploadContext context) { // record RPanFile
        saveFile(context);
        saveUserFile(context.getParentId(), context.getFilename(), FolderFlagEnum.NO,
                FileTypeEnum.getFileTypeCode(FileUtils.getFileSuffix(context.getFilename())),
                context.getRecord().getFileId(), context.getUserId(), context.getRecord().getFileSizeDesc());
    }

    /**
     * 文件分片上传
     * 标识符一致，用户 id 一致，这个分片就不需要上传了
     * <p>
     * 1、上传实体文件
     * 2、保存分片文件记录
     * 3、校验是否全部分片上传完成
     *
     * @param context
     * @return
     */
    @Override
    public FileChunkUploadVO chunkUpload(FileChunkUploadContext context) {
        FileChunkSaveContext fileChunkSaveContext = fileConverter.fileChunkUploadContext2FileChunkSaveContext(context);
        iFileChunkService.saveChunkFile(fileChunkSaveContext);
        FileChunkUploadVO vo = new FileChunkUploadVO();
        vo.setMergeFlag(fileChunkSaveContext.getMergeFlagEnum().getCode()); // 判断分片文件上传是否完成
        return vo;
    }

    /**
     * 查询用户已上传的文件分片列表
     *
     * @param context QueryUploadedChunksContext
     * @return
     */
    @Override
    public UploadedChunksVO getUploadedChunks(QueryUploadedChunksContext context) {
        QueryWrapper<RPanFileChunk> queryWrapper = Wrappers.query();
        queryWrapper.select("chunk_number");
        queryWrapper.eq("identifier", context.getIdentifier());
        queryWrapper.eq("create_user", context.getUserId());
        queryWrapper.gt("expiration_time", new Date()); // 没有过期的分片
        // 只返回第一个字段，就是 chunk_number
        List<Integer> uploadedChunks = iFileChunkService.listObjs(queryWrapper, value -> (Integer) value);
        UploadedChunksVO uploadedChunksVO = new UploadedChunksVO();
        uploadedChunksVO.setUploadedChunks(uploadedChunks);
        return uploadedChunksVO;
    }

    /**
     * 文件分片合并
     * 1、文件分片物理合并
     * 2、保存文件实体记录
     * 3、保存文件 user-file 映射
     *
     * @param context
     */
    @Override
    public void mergeFile(FileChunkMergeContext context) {
        mergeFileChunkAndSaveFile(context);
        saveUserFile(context.getParentId(), context.getFilename(), FolderFlagEnum.NO, FileTypeEnum.getFileTypeCode(FileUtils.getFileSuffix(context.getFilename())), context.getRecord().getFileId(), context.getUserId(), context.getRecord().getFileSizeDesc());
    }

    /**
     * 文件下载
     * 1. 参数校验，校验文件是否存在，校验文件是否属于该用户
     * 2. 校验该文件是不是一个文件夹
     * 3. 执行下载
     *
     * @param context
     */
    @Override
    public void download(FileDownloadContext context) {
        RPanUserFile record = getById(context.getFileId());
        checkOperatePermission(record, context.getUserId());
        if (checkIsFolder(record)) {
            throw new RPanBusinessException("文件夹不支持下载");
        }
        doDownload(record, context.getResponse());
    }

    /**
     * 文件下载，无需校验用户
     *
     * @param context
     */
    @Override
    public void downloadWithoutCheckUser(FileDownloadContext context) {
        RPanUserFile record = getById(context.getFileId());
        if (Objects.isNull(record)) {
            throw new RPanBusinessException("当前文件记录不存在");
        }
        if (checkIsFolder(record)) {
            throw new RPanBusinessException("文件夹不支持下载");
        }
        doDownload(record, context.getResponse());
    }

    /**
     * 文件预览
     * 1. 参数校验，校验文件是否存在，校验文件是否属于该用户
     * 2. 校验该文件是不是一个文件夹
     * 3. 执行预览
     *
     * @param context
     */
    @Override
    public void preview(FilePreviewContext context) {
        RPanUserFile record = getById(context.getFileId());
        checkOperatePermission(record, context.getUserId());
        if (checkIsFolder(record)) {
            throw new RPanBusinessException("文件夹不支持下载");
        }
        doPreview(record, context.getResponse());
    }

    /**
     * 查询用户的文件夹树（只查询了一次数据库）
     * 1、查询出该用户的所有文件夹列表
     * 2、在内存中拼装文件夹树
     *
     * @param context
     * @return
     */
    @Override
    public List<FolderTreeNodeVO> getFolderTree(QueryFolderTreeContext context) {
        List<RPanUserFile> folderRecords = queryFolderRecords(context.getUserId());
        List<FolderTreeNodeVO> result = assembleFolderTreeNodeVOList(folderRecords);
        return result;
    }

    /**
     * 文件转移
     * 1、权限校验
     *
     * @param context
     */
    @Override
    public void transfer(TransferFileContext context) {
        checkTransferCondition(context);
        doTransfer(context);
    }

    /**
     * 文件复制
     *
     * @param context
     */
    @Override
    public void copy(CopyFileContext context) {
        checkCopyCondition(context);
        doCopy(context);
    }

    /**
     * 文件列表搜索
     * 1、执行文件搜索
     * 2、拼装文件的父文件夹名称
     * 3、文件搜索的后置动作
     *
     * @param context
     * @return
     */
    @Override
    public List<FileSearchResultVO> search(FileSearchContext context) {
        String keyword = context.getKeyword();
        List<FileSearchResultVO> result = doSearch(context);
        fillParentFilename(result);
        afterSearch(context);
        return result;
    }

    /**
     * 获取面包屑列表
     * <p>
     * 1、获取用户所有文件夹信息
     * 2、拼接需要用到的面包屑的列表
     *
     * @param context
     * @return
     */
    @Override
    public List<BreadcrumbVO> getBreadcrumbs(QueryBreadcrumbsContext context) {
        List<RPanUserFile> folderRecords = queryFolderRecords(context.getUserId());
        Map<Long, BreadcrumbVO> prepareBreadcrumbVOMap = folderRecords.stream().map(BreadcrumbVO::transfer).collect(Collectors.toMap(BreadcrumbVO::getId, a -> a));
        BreadcrumbVO currentNode;
        Long fileId = context.getFileId();
        List<BreadcrumbVO> result = Lists.newLinkedList();
        do {
            currentNode = prepareBreadcrumbVOMap.get(fileId);
            if (Objects.nonNull(currentNode)) {
                result.add(0, currentNode);
                fileId = currentNode.getParentId();
            }
        } while (Objects.nonNull(currentNode));
        return result;
    }

    /**
     * 递归查询所有的文件（子文件）信息
     *
     * @param records
     * @return
     */
    @Override
    public List<RPanUserFile> findAllFileRecords(List<RPanUserFile> records) {
        List<RPanUserFile> result = Lists.newArrayList(records);
        if (CollectionUtils.isEmpty(records)) {
            return records;
        }
        long folderCount = result.stream().filter(record -> Objects.equals(record.getFolderFlag(), FolderFlagEnum.YES.getCode())).count();
        if (folderCount == 0) {
            return result;
        }
        records.forEach(record -> doFindAllChildRecords(result, record));
        return result;
    }

    /**
     * 根据文件 id 查找所有的未删除的文件（子文件）记录
     *
     * @param fileIdList
     * @return
     */
    @Override
    public List<RPanUserFile> findAllFileRecordsByFileIdList(List<Long> fileIdList) {
        if (CollectionUtils.isEmpty(fileIdList)) {
            return Lists.newArrayList();
        }
        List<RPanUserFile> records = listByIds(fileIdList);
        if (CollectionUtils.isEmpty(records)) {
            return Lists.newArrayList();
        }
        return findAllFileRecords(records);
    }

    /**
     * 实体转换
     *
     * @param records
     * @return
     */
    @Override
    public List<RPanUserFileVO> transferVOList(List<RPanUserFile> records) {
        if (Objects.isNull(records)) {
            return Lists.newArrayList();
        }
        return records.stream()
                .map(fileConverter::rPanUserFile2RPanUserFileVO)
                .collect(Collectors.toList());
    }

    /**
     * 递归查找所有的子文件，忽略删除标识
     *
     * @param result
     * @param record
     */

    private void doFindAllChildRecords(List<RPanUserFile> result, RPanUserFile record) {
        if (Objects.isNull(record)) {
            return;
        }
        if (!checkIsFolder(record)) {
            return;
        }
        List<RPanUserFile> childRecords = findChildRecordsIgnoreDelFlag(record.getFileId());
        if (CollectionUtils.isEmpty(childRecords)) {
            return;
        }
        result.addAll(childRecords);
        childRecords.stream()
                .filter(childRecord -> FolderFlagEnum.YES.getCode().equals(childRecord.getFolderFlag()))
                .forEach(childRecord -> doFindAllChildRecords(result, childRecord));
    }

    /**
     * 查询当前文件夹下面的文件记录，只查询一次子文件（夹），忽略删除标识
     *
     * @param fileId
     * @return
     */
    private List<RPanUserFile> findChildRecordsIgnoreDelFlag(Long fileId) {
        QueryWrapper<RPanUserFile> queryWrapper = Wrappers.query();
        queryWrapper.eq("parent_id", fileId);
        List<RPanUserFile> childRecords = list(queryWrapper);
        return childRecords;
    }

    //private void doFindAllChildRecords(List<RPanUserFile> result, RPanUserFile record) {
    //    if (Objects.isNull(record)) {
    //        return;
    //    }
    //    if (record.getDelFlag().equals(DelFlagEnum.NO.getCode())) {
    //        result.add(record);
    //    } else {
    //        return;
    //    }
    //    if (record.getFolderFlag().equals(FolderFlagEnum.YES.getCode())) {
    //        QueryWrapper<RPanUserFile> queryWrapper = Wrappers.query();
    //        queryWrapper.eq("parent_id", record.getFileId());
    //        List<RPanUserFile> children = list(queryWrapper);
    //        for (RPanUserFile child : children) {
    //            doFindAllChildRecords(result, child);
    //        }
    //    }
    //}

    /**
     * 搜索的后置操作，发布文件搜索事件，更新用户的搜索历史列表
     *
     * @param context
     */
    private void afterSearch(FileSearchContext context) {
        UserSearchEvent userSearchEvent = new UserSearchEvent(context.getKeyword(), context.getUserId());
        // todo 用户搜索历史的事件
//        rabbitTemplate.convertAndSend(RabbitMQConfig.USER_SEARCH_EXCHANGE,
//                "userSearch.send", JSON.toJSONString(userSearchEvent));
        //producer.sendMessage(PanChannels.USER_SEARCH_OUTPUT, event);
        //applicationContext.publishEvent(event);
    }

    /**
     * 填充文件列表的父文件夹名称
     *
     * @param result
     */
    private void fillParentFilename(List<FileSearchResultVO> result) {
        if (CollectionUtils.isEmpty(result)) {
            return;
        }
        List<Long> parentIdList = result.stream().map(FileSearchResultVO::getParentId).collect(Collectors.toList());
        List<RPanUserFile> parentRecords = listByIds(parentIdList);
        Map<Long, String> fileId2filenameMap = parentRecords.stream()
                .collect(Collectors.toMap(RPanUserFile::getFileId, RPanUserFile::getFilename));
        result.forEach(vo -> vo.setParentFilename(fileId2filenameMap.get(vo.getParentId())));
    }

    private List<FileSearchResultVO> doSearch(FileSearchContext context) {
        return baseMapper.searchFile(context);
    }

    /**
     * 执行文件复制的动作
     *
     * @param context
     */
    private void doCopy(CopyFileContext context) {
        List<RPanUserFile> prepareRecords = context.getPrepareRecords();
        if (CollectionUtils.isNotEmpty(prepareRecords)) {
            List<RPanUserFile> allRecords = Lists.newArrayList();
            prepareRecords.forEach(record -> {
                assembleCopyChildRecord(allRecords, record, context.getTargetParentId(), context.getUserId());
            });
            if (!saveBatch(allRecords)) {
                throw new RPanBusinessException("文件复制失败");
            }
        }
    }

    /**
     * 递归修改文件（文件夹）信息
     *
     * @param allRecords
     * @param record
     * @param targetParentId
     * @param userId
     */
    private void assembleCopyChildRecord(List<RPanUserFile> allRecords, RPanUserFile record, Long targetParentId, Long userId) {
        // 根节点是一个文件夹（文件）
        Long newFileId = IdUtil.get();
        Long oldFileId = record.getFileId();
        record.setParentId(targetParentId);
        record.setFileId(newFileId);
        record.setUserId(userId);
        record.setCreateUser(userId);
        record.setCreateTime(new Date());
        record.setUpdateUser(userId);
        record.setUpdateTime(new Date());
        handleDuplicateFilename(record);
        allRecords.add(record);
        // 当前节点是一个文件夹
        if (checkIsFolder(record)) {
            List<RPanUserFile> childRecords = findChildRecords(oldFileId);
            if (CollectionUtils.isEmpty(childRecords)) {
                return;
            }
            childRecords.forEach(childRecord -> {
                // 文件和文件夹都要递归下去
                assembleCopyChildRecord(allRecords, childRecord, newFileId, userId);
            });
        }
    }

    private List<RPanUserFile> findChildRecords(Long parentId) {
        QueryWrapper<RPanUserFile> queryWrapper = Wrappers.query();
        queryWrapper.eq("parent_id", parentId);
        queryWrapper.eq("del_flag", DelFlagEnum.NO.getCode());
        return list(queryWrapper);
    }

    /**
     * 校验的条件和转移文件夹一致
     *
     * @param context
     */
    private void checkCopyCondition(CopyFileContext context) {
        Long targetParentId = context.getTargetParentId();
        if (!checkIsFolder(getById(targetParentId))) {
            throw new RPanBusinessException("目标文件不是一个文件夹");
        }
        List<Long> fileIdList = context.getFileIdList();
        List<RPanUserFile> prepareRecords = listByIds(fileIdList);
        context.setPrepareRecords(prepareRecords);
        if (checkIsChildFolder(prepareRecords, targetParentId, context.getUserId())) {
            throw new RPanBusinessException("目标文件夹不能是选中文件列表中的文件夹及其子文件夹");
        }
    }

    private void doTransfer(TransferFileContext context) {
        List<RPanUserFile> prepareRecords = context.getPrepareRecords();
        prepareRecords.forEach(record -> {
            record.setParentId(context.getTargetParentId());
            record.setUserId(context.getUserId());
            record.setCreateUser(context.getUserId());
            record.setCreateTime(new Date());
            record.setUpdateUser(context.getUserId());
            record.setUpdateTime(new Date());
            // 处理文件名重复
            handleDuplicateFilename(record);
        });
        if (!updateBatchById(prepareRecords)) {
            throw new RPanBusinessException("文件转移失败");
        }
    }

    /**
     * 文件转移的条件校验
     * 1、目标文件必须是一个文件夹
     * 2、选中的要转移的文件列表中不能含有目标文件夹及其子文件夹，否则会有循环引用
     * 3、根节点不能转移到自己的子节点上
     *
     * @param context TransferFileContext
     */
    private void checkTransferCondition(TransferFileContext context) {
        Long targetParentId = context.getTargetParentId();
        if (!checkIsFolder(getById(targetParentId))) {
            throw new RPanBusinessException("目标文件不是一个文件夹");
        }
        List<Long> fileIdList = context.getFileIdList();
        List<RPanUserFile> prepareRecords = listByIds(fileIdList);
        context.setPrepareRecords(prepareRecords);
        if (checkIsChildFolder(prepareRecords, targetParentId, context.getUserId())) {
            throw new RPanBusinessException("目标文件夹不能是选中文件列表中的文件夹及其子文件夹");
        }
    }

    /**
     * 校验目标文件夹 ID 是否是要操作的文件记录的文件夹 ID 及其子文件夹 ID
     * 1、文件列表中没有文件夹，直接返回 false
     * 2、拼装文件夹 id 和所有的文件夹 id，判断存在即可
     *
     * @param prepareRecords
     * @param targetParentId
     * @param userId
     * @return
     */
    private boolean checkIsChildFolder(List<RPanUserFile> prepareRecords, Long targetParentId, Long userId) {
        // 把文件夹过滤出来
        prepareRecords = prepareRecords.stream()
                .filter(record -> Objects.equals(record.getFolderFlag(), FolderFlagEnum.YES.getCode()))
                .collect(Collectors.toList());
        if (prepareRecords.isEmpty()) {
            return false;
        }
        // 构建出这样一张图
        List<RPanUserFile> folderRecords = queryFolderRecords(userId);
        // 当前用户的文件夹图（树）
        Map<Long, List<RPanUserFile>> folderRecordMap =
                folderRecords.stream().collect(Collectors.groupingBy(RPanUserFile::getParentId));
        List<RPanUserFile> unavailableFolderRecords = Lists.newArrayList();
        unavailableFolderRecords.addAll(prepareRecords);
        // 递归查找所有的子文件夹
        prepareRecords.forEach(record -> findAllChildRecords(unavailableFolderRecords, folderRecordMap, record));
        List<Long> unavailableFolderRecordIds = unavailableFolderRecords.stream().map(RPanUserFile::getFileId).collect(Collectors.toList());
        return unavailableFolderRecordIds.contains(targetParentId);
    }

    /**
     * 查找文件夹所有的子文件夹记录
     *
     * @param unavailableFolderRecords
     * @param folderRecordMap
     * @param record
     */
    private void findAllChildRecords(List<RPanUserFile> unavailableFolderRecords,
                                     Map<Long, List<RPanUserFile>> folderRecordMap,
                                     RPanUserFile record) {
        if (Objects.isNull(record)) {
            return;
        }
        // 拿到当前节点的所有子文件夹
        List<RPanUserFile> childFolderRecords = folderRecordMap.get(record.getFileId());
        if (CollectionUtils.isEmpty(childFolderRecords)) {
            return;
        }
        // 前置遍历
        unavailableFolderRecords.addAll(childFolderRecords);
        // 递归，DFS
        childFolderRecords.forEach(childRecord -> findAllChildRecords(unavailableFolderRecords, folderRecordMap, childRecord)); // 递归
    }

    /**
     * 拼装文件夹树列表
     *
     * @param folderRecords
     * @return
     */
    private List<FolderTreeNodeVO> assembleFolderTreeNodeVOList(List<RPanUserFile> folderRecords) {
        if (CollectionUtils.isEmpty(folderRecords)) {
            return Lists.newArrayList();
        }
        // Java 8 新特性 Stream
        List<FolderTreeNodeVO> mappedFolderTreeNodeVOList = folderRecords.stream()
                .map(fileConverter::rPanUserFile2FolderTreeNodeVO)
                .collect(Collectors.toList());
        Map<Long, List<FolderTreeNodeVO>> map = mappedFolderTreeNodeVOList
                .stream()
                .collect(Collectors.groupingBy(FolderTreeNodeVO::getParentId));
        for (FolderTreeNodeVO node : mappedFolderTreeNodeVOList) {
            List<FolderTreeNodeVO> children = map.get(node.getId());
            if (CollectionUtils.isNotEmpty(children)) {
                node.getChildren().addAll(children);
            }
        }
        // 只返回根节点
        return mappedFolderTreeNodeVOList
                .stream()
                .filter(node -> Objects.equals(node.getParentId(), FileConstants.TOP_PARENT_ID))
                .collect(Collectors.toList());
    }

    /**
     * 查询用户所有有效的文件夹信息
     *
     * @param userId
     * @return
     */
    private List<RPanUserFile> queryFolderRecords(Long userId) {
        QueryWrapper<RPanUserFile> queryWrapper = Wrappers.query();
        queryWrapper.eq("user_id", userId);
        queryWrapper.eq("folder_flag", FolderFlagEnum.YES.getCode());
        queryWrapper.eq("del_flag", DelFlagEnum.NO.getCode());
        return list(queryWrapper);
    }

    /**
     * 执行文件预览的动作
     * 1、查询文件的真实存储路径
     * 2、添加跨域的公共响应头
     * 3、委托文件存储引擎去读取文件内容到响应的输出流中
     *
     * @param record   RPanUserFile
     * @param response HttpServletResponse
     */
    private void doPreview(RPanUserFile record, HttpServletResponse response) {
        RPanFile realFileRecord = iFileService.getById(record.getRealFileId());
        if (Objects.isNull(realFileRecord)) {
            throw new RPanBusinessException("当前的文件记录不存在");
        }
        addCommonResponseHeader(response, realFileRecord.getFilePreviewContentType());
        realFile2OutputStream(realFileRecord.getRealPath(), response);
    }

    /**
     * 执行文件下载
     * 1、查询文件的真实存储路径
     * 2、添加跨域的公共响应头（发生到浏览器中，浏览器不允许）下载没有跨域，浏览器会直接拦截
     * 3、拼装响应信息
     * 4、文件存储引擎读取文件内容到输出流中
     *
     * @param record
     * @param response
     */
    private void doDownload(RPanUserFile record, HttpServletResponse response) {
        RPanFile realFileRecord = iFileService.getById(record.getRealFileId());
        if (Objects.isNull(realFileRecord)) {
            throw new RPanBusinessException("当前的文件记录不存在");
        }
        addCommonResponseHeader(response, MediaType.APPLICATION_OCTET_STREAM_VALUE);
        addDownloadAttribute(response, record, realFileRecord);
        realFile2OutputStream(realFileRecord.getRealPath(), response);
    }

    private void realFile2OutputStream(String realPath, HttpServletResponse response) {
        try {
            ReadFileContext readFileContext = new ReadFileContext();
            readFileContext.setRealPath(realPath);
            readFileContext.setOutputStream(response.getOutputStream());
            storageEngine.readFile(readFileContext);
        } catch (IOException e) {
            e.printStackTrace();
            throw new RPanBusinessException("文件下载失败");
        }
    }

    /**
     * 添加文件下载的属性信息
     *
     * @param response       response
     * @param record
     * @param realFileRecord
     */
    private void addDownloadAttribute(HttpServletResponse response, RPanUserFile record, RPanFile realFileRecord) {
        try {
            response.addHeader(FileConstants.CONTENT_DISPOSITION_STR,
                    FileConstants.CONTENT_DISPOSITION_VALUE_PREFIX_STR + new String(record.getFilename()
                            .getBytes(FileConstants.GB2312_STR), FileConstants.IOS_8859_1_STR));
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
            throw new RPanBusinessException("文件下载失败");
        }
        response.setContentLengthLong(Long.valueOf(realFileRecord.getFileSize()));
    }

    private void addCommonResponseHeader(HttpServletResponse response, String contentTypeValue) {
        response.reset();
        HttpUtil.addCorsResponseHeaders(response);
        response.addHeader(FileConstants.CONTENT_TYPE_STR, contentTypeValue);
        response.setContentType(contentTypeValue);
    }


    /**
     * 校验用户的操作权限
     * 1、文件记录必须存在
     * 2、文件记录创建必须是该登录用户
     *
     * @param record
     * @param userId
     */
    private void checkOperatePermission(RPanUserFile record, Long userId) {
        if (Objects.isNull(record)) {
            throw new RPanBusinessException("文件为空");
        }
        if (!record.getUserId().equals(userId)) {
            throw new RPanBusinessException("您没有该文件的下载权限");
        }
    }

    private boolean checkIsFolder(RPanUserFile record) {
        if (Objects.isNull(record)) {
            throw new RPanBusinessException("文件为空");
        }
        return FolderFlagEnum.YES.getCode().equals(record.getFolderFlag());
    }


    /**
     * 合并文件分片并保存物理文件记录
     *
     * @param context
     */
    private void mergeFileChunkAndSaveFile(FileChunkMergeContext context) {
        FileChunkMergeAndSaveContext fileChunkMergeAndSaveContext =
                fileConverter.fileChunkMergeContext2FileChunkMergeAndSaveContext(context);
        iFileService.mergeFileChunkAndSaveFile(fileChunkMergeAndSaveContext);
        context.setRecord(fileChunkMergeAndSaveContext.getRecord());
    }

    /**
     * 上传文件并保存实体文件记录
     * 实体文件的 service 来处理
     *
     * @param context
     */
    private void saveFile(FileUploadContext context) {
        FileSaveContext fileSaveContext = fileConverter.fileUploadContext2FileSaveContext(context);
        iFileService.saveFile(fileSaveContext);
        context.setRecord(fileSaveContext.getRecord());
    }

    private RPanFile getFileByUserIdAndIdentifier(Long userId, String identifier) {
        QueryWrapper<RPanFile> queryWrapper = Wrappers.query();
        queryWrapper.eq("create_user", userId);
        queryWrapper.eq("identifier", identifier);
        List<RPanFile> records = iFileService.list(queryWrapper);
        if (CollectionUtils.isEmpty(records)) {
            return null;
        }
        return records.get(RPanConstants.ZERO_INT);
    }

    private void afterFileDelete(DeleteFileContext context) {
        // ApplicationContext 实现
        // 注入实体 Bean this
        // todo 发布文件删除事件
        DeleteFileEvent deleteFileEvent = new DeleteFileEvent(context.getFileIdList());
//        rabbitTemplate.convertAndSend(RabbitMQConfig.DELETE_FILE_EXCHANGE,
//                "deleteFile.send", JSON.toJSONString(deleteFileEvent));
        //DeleteFileEvent deleteFileEvent = new DeleteFileEvent(this, context.getFileIdList());
        //applicationContext.publishEvent(deleteFileEvent);
        //DeleteFileEvent deleteFileEvent = new DeleteFileEvent(context.getFileIdList());
        //producer.sendMessage(PanChannels.DELETE_FILE_OUTPUT, deleteFileEvent);
        //applicationContext.publishEvent(deleteFileEvent);
    }

    private void doDeleteFile(DeleteFileContext context) {
        List<Long> fileIdList = context.getFileIdList();
        UpdateWrapper<RPanUserFile> updateWrapper = new UpdateWrapper<>();
        updateWrapper.in("file_id", fileIdList);
        updateWrapper.set("del_flag", DelFlagEnum.YES.getCode());
        updateWrapper.set("update_time", new Date());
        if (!update(updateWrapper)) {
            throw new RPanBusinessException("文件删除失败");
        }
    }

    /**
     * 删除文件的前置校验
     * 文件 id 集合不能有重复记录
     * 文件 id 集合中的文件都必须属于当前用户
     *
     * @param context DeleteFileContext
     */
    private void checkFileDeleteCondition(DeleteFileContext context) {
        List<Long> fileIdList = context.getFileIdList();
        List<RPanUserFile> rPanUserFiles = listByIds(fileIdList);
        if (rPanUserFiles.size() != fileIdList.size()) {
            throw new RPanBusinessException("存在文件不合法记录");
        }
        Set<Long> fileIdSet = rPanUserFiles.stream().map(RPanUserFile::getFileId).collect(Collectors.toSet());
        int oldSize = fileIdSet.size();
        fileIdSet.addAll(fileIdList);
        int newSize = fileIdSet.size();
        if (oldSize != newSize) {
            throw new RPanBusinessException("存在文件不合法记录");
        }
        Set<Long> userIdSet = rPanUserFiles.stream().map(RPanUserFile::getUserId).collect(Collectors.toSet());
        if (userIdSet.size() != 1) {
            throw new RPanBusinessException("存在文件不合法记录");
        }
        Long dbUserId = userIdSet.stream().findFirst().get();
        if (!Objects.equals(dbUserId, context.getUserId())) {
            throw new RPanBusinessException("当前登录用户没有删除该文件的权限");
        }
    }

    private void doUpdateFilename(UpdateFilenameContext context) {
        RPanUserFile entity = context.getEntity();
        entity.setFilename(context.getNewFilename());
        entity.setUpdateUser(context.getUserId());
        entity.setUpdateTime(new Date());
        if (!updateById(entity)) {
            throw new RPanBusinessException("文件重命名失败");
        }
    }

    /**
     * 校验用户更新文件名的权限
     * 1. 文件名有效
     * 2. 用户有权限更新该文件的文件名
     * 3. 新旧文件名字不能相同
     * 4. 不能使用当前文件夹下子文件的名称
     *
     * @param context UpdateFilenameContext
     */
    private void checkUpdateFilenameCondition(UpdateFilenameContext context) {
        Long fileId = context.getFileId();
        RPanUserFile entity = getById(fileId);
        if (Objects.isNull(entity)) {
            throw new RPanBusinessException("该文件ID无效");
        }
        if (!Objects.equals(entity.getUserId(), context.getUserId())) {
            throw new RPanBusinessException("用户无权修改该文件的文件名");
        }
        if (Objects.equals(entity.getFilename(), context.getNewFilename())) {
            throw new RPanBusinessException("新旧文件名不能相同");
        }
        QueryWrapper<RPanUserFile> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("parent_id", entity.getParentId());
        queryWrapper.eq("filename", context.getNewFilename());
        int count = count(queryWrapper);
        if (count > 0) {
            throw new RPanBusinessException("该文件名在子文件夹下已存在");
        }
        context.setEntity(entity);
    }

    /****** private ******/

    /**
     * 生成一个 RPanUserFile 对象，同时处理重复命名的问题，然后存入表中
     *
     * @param parentId       父文件夹
     * @param filename       文件名
     * @param folderFlagEnum 是否是文件夹
     * @param fileType       文件类型
     * @param realFileId     真实文件 id
     * @param userId         用户 id
     * @param fileSizeDesc   文件大小
     * @return UserFile id
     */
    private Long saveUserFile(Long parentId, String filename, FolderFlagEnum folderFlagEnum,
                              Integer fileType, Long realFileId, Long userId, String fileSizeDesc) {
        RPanUserFile entity = assembleRPanFUserFile(parentId, userId, filename, folderFlagEnum, fileType, realFileId, fileSizeDesc);
        if (!save((entity))) {
            throw new RPanBusinessException("保存文件信息失败");
        }
        return entity.getFileId();
    }

    /**
     * 根据入参生成 RPanUserFile 对象
     * 构建并填充实体，并处理文件命名一致的问题
     * a -> b, a -> b (1)
     *
     * @param parentId
     * @param userId
     * @param filename
     * @param folderFlagEnum
     * @param fileType
     * @param realFileId
     * @param fileSizeDesc
     * @return
     */
    private RPanUserFile assembleRPanFUserFile(Long parentId, Long userId, String filename, FolderFlagEnum folderFlagEnum, Integer fileType, Long realFileId, String fileSizeDesc) {
        RPanUserFile entity = new RPanUserFile();
        entity.setFileId(IdUtil.get());
        entity.setUserId(userId);
        entity.setParentId(parentId);
        entity.setRealFileId(realFileId);
        entity.setFilename(filename);
        entity.setFolderFlag(folderFlagEnum.getCode());
        entity.setFileSizeDesc(fileSizeDesc);
        entity.setFileType(fileType);
        entity.setDelFlag(DelFlagEnum.NO.getCode());
        entity.setCreateUser(userId);
        entity.setCreateTime(new Date());
        entity.setUpdateUser(userId);
        entity.setUpdateTime(new Date());
        handleDuplicateFilename(entity);
        return entity;
    }

    /**
     * 处理用户重复名称
     * 同一文件下面有文件名重复
     * 按照系统规则重命名文件夹（1）
     *
     * @param entity
     */
    private void handleDuplicateFilename(RPanUserFile entity) {
        String filename = entity.getFilename(), newFilenameWithoutSuffix, newFilenameSuffix;
        int newFilenamePointPosition = filename.lastIndexOf(RPanConstants.POINT_STR);
        if (newFilenamePointPosition == RPanConstants.MINUS_ONE_INT) {
            newFilenameWithoutSuffix = filename;
            newFilenameSuffix = StringUtils.EMPTY;
        } else {
            newFilenameWithoutSuffix = filename.substring(RPanConstants.ZERO_INT, newFilenamePointPosition);
            newFilenameSuffix = filename.replace(newFilenameWithoutSuffix, StringUtils.EMPTY);
        }
        List<RPanUserFile> existRecords = getDuplicateFilename(entity, newFilenameWithoutSuffix);
        if (CollectionUtils.isEmpty(existRecords)) {
            return;
        }
        List<String> existFilenames = existRecords.stream().map(RPanUserFile::getFilename).collect(Collectors.toList());
        int count = 1;
        String newFilename;
        do {
            newFilename = assembleNewDuplicateFilename(newFilenameWithoutSuffix, count, newFilenameSuffix);
            count++;
        } while (existFilenames.contains(newFilename));
        entity.setFilename(newFilename);
    }

    /**
     * 拼装新文件名称
     * 如 a -> a（3）
     * 拼装规则参考操作系统重复文件名称的重命名规范
     *
     * @param newFilenameWithoutSuffix
     * @param count
     * @param newFilenameSuffix
     * @return
     */
    private String assembleNewDuplicateFilename(String newFilenameWithoutSuffix, int count, String newFilenameSuffix) {
        String newFilename = new StringBuilder(newFilenameWithoutSuffix)
                .append(FileConstants.CN_LEFT_PARENTHESES_STR)
                .append(count)
                .append(FileConstants.CN_RIGHT_PARENTHESES_STR)
                .append(newFilenameSuffix).toString();
        return newFilename;
    }

    /**
     * 查找同一父文件夹下面的同名文件数量
     *
     * @param entity                   RPanUserFile
     * @param newFilenameWithoutSuffix
     * @return
     */
    private List<RPanUserFile> getDuplicateFilename(RPanUserFile entity, String newFilenameWithoutSuffix) {
        QueryWrapper<RPanUserFile> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("parent_id", entity.getParentId());
        queryWrapper.eq("folder_flag", entity.getFolderFlag());
        queryWrapper.eq("user_id", entity.getUserId());
        queryWrapper.eq("del_flag", DelFlagEnum.NO.getCode());
        queryWrapper.likeRight("filename", newFilenameWithoutSuffix);
        return list(queryWrapper);
    }
}